﻿/* 
 * Evo
 * 
 * Marcin Krupowicz (marcin@krupowicz.eu)
 * Barbara Szymanek (basia.szymanek@gmail.com)
 * 
 * January, 2009
 *
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace Evo.Operators
{
    #region Selection Operators Class
    //return population of the same size as input population
    public class BlockSelectionOperator<T> : IOperator<T> where T : IIndividual
    {
        private int blockSize;

        public BlockSelectionOperator(int blockSize)
        {
            this.blockSize = blockSize;
        }

        public Population<T> Execute(Population<T> population)
        {
           return Selection<T>.BlockSelection(population, blockSize);
        }

        public override string ToString()
        {
            return String.Format("{0}\n\tblock size {1}\n", 
                "BlockSelectionOperator", blockSize);
        }
    }

    public class RouletteSelectionOperator<T> : IOperator<T> where T : IIndividual
    {
        private int populationSize;

        public RouletteSelectionOperator(int populationSize)
        {
            this.populationSize = populationSize;
        }
        

        public Population<T> Execute(Population<T> population)
        {

            return Selection<T>.RouletteSelection(population, populationSize);
        }

        public override string ToString()
        {
            return String.Format("{0}\n\tpopulation size: {1}\n", "RouletteSelectionOperator", populationSize);
        }

    }

    public class TournamentSelectionOperator<T> : IOperator<T> where T : IIndividual
    {
        private int tournamentSize;
        private int populationSize;
        private int winnerNumbers;
        public TournamentSelectionOperator(int populationSize, int tournamentSize, int winnerNumbers)
        {
            this.populationSize = populationSize;
            this.tournamentSize = tournamentSize;
            this.winnerNumbers = winnerNumbers;
        }

        public Population<T> Execute(Population<T> population)
        {
            return Selection<T>.TournamentSelection(population, populationSize, tournamentSize, winnerNumbers);
        }

        public override string ToString()
        {
            return String.Format("{0}\n\ttournament size: {1}\n\twinner numbers: {2}\t", 
                "TournamentSelectionOperator", tournamentSize, winnerNumbers);
        }
    }

    public class KBestSelectionOperator<T> : IOperator<T> where T : IIndividual
    {
        int bestNumber;

        public KBestSelectionOperator(int bestNumber)
        {
            this.bestNumber = bestNumber;
        }

        public Population<T> Execute(Population<T> population)
        {
            return Selection<T>.KBestSelection(population, bestNumber);
        }

        public override string ToString()
        {
            return String.Format("{0}\n\tbest number: {1}\n",
                "KBestSelectionOperator", bestNumber);
        }
    }
    #endregion

    #region Static Selection
    public class Selection<T> where T : IIndividual
	{
		protected static Random random = new Random(DateTime.Now.Millisecond);

		/// <summary>
		/// Select best individuals with roulette method
		/// </summary>
		/// <param name="population">population on which selection is performed</param>
		/// <param name="quantity">quantity of individuals which are selected</param>
		/// <returns>New population</returns>
		public static Population<T> RouletteSelection(Population<T> population, int quantity)
		{
			List<T> individuals = new List<T>(quantity);
			double[] probabilities = new double[population.Size];
			
			//NOTE: overall sum of probabilities must be equal to 1.0
			for (int i = 0; i < population.Size; i++)       
				probabilities[i] = population[i].Fitness;

			double left, right;
			for (int i = 0; i < quantity; i++)
			{
				double r = random.NextDouble();
				left = 0.0;
				for (int j = 0; j < probabilities.Length; j++)
				{
					right = left + probabilities[j];
					if (left <= r && r <= right)
						individuals.Add((T)population[j].Clone());
                    
					left = right;
				}
			}
          
			return new Population<T>(individuals);
		}

		/// <summary>
		/// Select K best individual
		/// </summary>
		/// <param name="population">population on which selection is performed</param>
		/// <param name="quantity">quantity of individuals which are selected</param>
		/// <returns>New population</returns>
		public static Population<T> KBestSelection(Population<T> population, int quantity)
		{
			if (quantity > population.Size)
				throw new Exception("Quantity is bigger than population size");
			
			List<T> individuals = new List<T>(quantity);
			population.Individuals.Sort();
			for (int i = 0; i < quantity; i++)
			{
				individuals.Add((T)population[i].Clone());
			}
			return new Population<T>(individuals);
		}


        public static Population<T> BlockSelection(Population<T> population, int blockSize)
        {
            population.Individuals.Sort();
    
            for (int i = 0, j = population.Individuals.Count - 1; i < blockSize; i++, j--)
            {
                population[j] = (T)population[i].Clone();
            }
            return population;
        }

		/// <summary>
		/// Select best individuals with tournament selection method
		/// </summary>
		/// <param name="population">population on which selection is performed</param>
		/// <param name="quantity">quantity of individuals which are selected</param>
		/// <param name="tournamentSize">size of tournament</param>
		/// <returns>New population</returns>
		public static Population<T> TournamentSelection(Population<T> population, int quantity,
            int tournamentSize, int winnerNumbers)
		{
			List<T> individuals = new List<T>(quantity);
			int populationSize = population.Size;

            if (winnerNumbers > tournamentSize)
                winnerNumbers = 1;

			for (int i = 0; i < quantity; i+= winnerNumbers)
			{
				List<T> tournament = new List<T>(tournamentSize);

				for (int j = 0; j < tournamentSize; j++)
					tournament.Add(population[random.Next(0, populationSize)]); //NOTE: populationSize - 1?
				
                tournament.Sort();

                for(int j = 0; j<winnerNumbers; j++)
				    individuals.Add((T)tournament[j].Clone());
			}

            if (individuals.Count > quantity)
                individuals.RemoveRange(quantity, individuals.Count - quantity);

			return new Population<T>(individuals);
		}
    }
    #endregion
}
